package com.sh.data.engine.job.admin.core.util;

import com.sh.data.engine.job.admin.core.model.EngineScheduleInfo;
import com.sh.data.engine.job.core.util.DateUtil;
import org.apache.commons.lang3.math.NumberUtils;

import java.time.Instant;
import java.util.Calendar;
import java.util.Date;
import java.util.Objects;

public class EngineScheduleUtil {

    public static Date getNextExecutionTime(EngineScheduleInfo scheduleInfo, Date lastExecutionTime) {

        int cycle = scheduleInfo.getCycle();
        String executionPoint = scheduleInfo.getExecPoint();
        Date effectiveDateStart = scheduleInfo.getEffectiveDateStart();

        //生效结束日期+1天
        Date effectiveDateEnd = scheduleInfo.getEffectiveDateEnd();
        effectiveDateEnd = DateUtil.addDays(effectiveDateEnd, 1);

        Integer startTimeHour = scheduleInfo.getStartTimeHour();
        Integer startTimeMin = scheduleInfo.getStartTimeMin();
        Integer endTimeHour = scheduleInfo.getEndTimeHour();
        Integer endTimeMin = scheduleInfo.getEndTimeMin();

        // 月调度计算下次执行周期
        boolean notContinue =
            Objects.isNull(startTimeHour) || Objects.isNull(startTimeMin) || Objects.isNull(
                effectiveDateStart) || Objects.isNull(effectiveDateEnd);

        if (notContinue) {
            return null;
        }
        if (cycle == TaskScheduleCycle.month.getKey()) {

            return computeMonthSchedule(
                executionPoint,
                effectiveDateStart,
                effectiveDateEnd,
                lastExecutionTime,
                startTimeHour,
                startTimeMin);

            // 周调度计算下次执行周期
        } else if (cycle == TaskScheduleCycle.week.getKey()) {

            return computeWeekSchedule(
                executionPoint,
                effectiveDateStart,
                effectiveDateEnd,
                lastExecutionTime,
                startTimeHour,
                startTimeMin);

            // 天调度计算下次执行周期
        } else if (cycle == TaskScheduleCycle.day.getKey()) {

            return computeDaySchedule(
                effectiveDateStart, effectiveDateEnd, lastExecutionTime, startTimeHour, startTimeMin);

            // 小时调度计算下次执行周期
        } else if (cycle == TaskScheduleCycle.hour.getKey()) {

            return computeHourSchedule(
                Integer.valueOf(executionPoint),
                effectiveDateStart,
                effectiveDateEnd,
                lastExecutionTime,
                startTimeHour,
                startTimeMin,
                endTimeHour,
                endTimeMin);

            // 分钟调度计算下次执行周期
        } else if (cycle == TaskScheduleCycle.minute.getKey()) {

            return computeMinuteSchedule(
                Integer.valueOf(executionPoint),
                effectiveDateStart,
                effectiveDateEnd,
                lastExecutionTime,
                startTimeHour,
                startTimeMin,
                endTimeHour,
                endTimeMin);
        }

        return null;
    }

    /**
     * 按分钟计算下个执行时间
     *
     * @param executionPoint
     * @param effectiveDateStart
     * @param effectiveDateEnd
     * @param lastExecutionTime
     * @param startTimeHour
     * @param startTimeMin
     * @param endTimeHour
     * @param endTimeMin
     * @return
     */
    private static Date computeMinuteSchedule(
        int executionPoint,
        Date effectiveDateStart,
        Date effectiveDateEnd,
        Date lastExecutionTime,
        int startTimeHour,
        int startTimeMin,
        int endTimeHour,
        int endTimeMin) {

        Calendar nextTime = Calendar.getInstance();
        // 计划每天执行日期
        Date current = new Date();
        nextTime.setTime(current);
        nextTime.set(Calendar.HOUR_OF_DAY, startTimeHour);
        nextTime.set(Calendar.MINUTE, startTimeMin);
        nextTime.set(Calendar.SECOND, 0);
        nextTime.set(Calendar.MILLISECOND, 0);

        // 计划每天截止日期
        Calendar end = Calendar.getInstance();
        end.setTime(nextTime.getTime());
        end.set(Calendar.HOUR_OF_DAY, endTimeHour);
        end.set(Calendar.MINUTE, endTimeMin);
        end.set(Calendar.SECOND, 0);
        end.set(Calendar.MILLISECOND, 0);

        if (lastExecutionTime != null) {

            if (nextTime.getTimeInMillis() <= lastExecutionTime.getTime()) {
                // 递归加一个时间间隔 直到大于当前时间
                addMinute(nextTime, lastExecutionTime, executionPoint);

                if (nextTime.getTimeInMillis() == lastExecutionTime.getTime()) {
                    nextTime.add(Calendar.MINUTE, executionPoint);
                }

                end.setTime(nextTime.getTime());
                end.set(Calendar.HOUR_OF_DAY, endTimeHour);
                end.set(Calendar.MINUTE, endTimeMin);
                end.set(Calendar.SECOND, 0);
                end.set(Calendar.MILLISECOND, 0);

                // 超过当天的计划执行截止日期加1天
                if (nextTime.getTimeInMillis() > end.getTimeInMillis()) {
                    nextTime.add(Calendar.MINUTE, -executionPoint);
                    nextTime.set(Calendar.HOUR_OF_DAY, startTimeHour);
                    nextTime.set(Calendar.MINUTE, startTimeMin);
                    nextTime.add(Calendar.DATE, 1);
                }
            }
        }

        // 如果下次执行时间小于当前时间 只能下个月执行
        if (nextTime.getTimeInMillis() < current.getTime()) {

            // 递归加一个时间间隔 直到大于当前时间
            addMinute(nextTime, current, executionPoint);

            if (nextTime.getTimeInMillis() > end.getTimeInMillis()) {
                nextTime.add(Calendar.MINUTE, -executionPoint);
                nextTime.set(Calendar.HOUR_OF_DAY, startTimeHour);
                nextTime.set(Calendar.MINUTE, startTimeMin);
                nextTime.add(Calendar.DATE, 1);
            }
        }

        // 如果下次执行日期小于有效开始日期
        if (nextTime.getTimeInMillis() < effectiveDateStart.getTime()) {

            nextTime.setTime(effectiveDateStart);
            nextTime.set(Calendar.HOUR_OF_DAY, startTimeHour);
            nextTime.set(Calendar.MINUTE, startTimeMin);
            nextTime.set(Calendar.SECOND, 0);
            nextTime.set(Calendar.MILLISECOND, 0);
        }

        Date NextExecutionTime = nextTime.getTime();
        // 下个计划执行日期超过有效期 则该任务不需要再执行
        if (null != effectiveDateEnd && NextExecutionTime.getTime() > effectiveDateEnd.getTime()) {
            return null;
        }
        return nextTime.getTime();
    }

    private static void addMinute(Calendar nextTime, Date date, int min) {

        if (nextTime.getTimeInMillis() >= date.getTime()) {
            return;
        }

        nextTime.add(Calendar.MINUTE, min);

        addMinute(nextTime, date, min);
    }

    /**
     * 按小时计算下个执行时间
     *
     * @param executionPoint
     * @param effectiveDateStart
     * @param effectiveDateEnd
     * @param lastExecutionTime
     * @param startTimeHour
     * @param startTimeMin
     * @param endTimeHour
     * @param endTimeMin
     * @return
     */
    private static Date computeHourSchedule(
        int executionPoint,
        Date effectiveDateStart,
        Date effectiveDateEnd,
        Date lastExecutionTime,
        int startTimeHour,
        int startTimeMin,
        int endTimeHour,
        int endTimeMin) {

        Calendar nextTime = Calendar.getInstance();
        // 计划每天执行日期
        Date current = new Date();
        nextTime.setTime(current);
        nextTime.set(Calendar.HOUR_OF_DAY, startTimeHour);
        nextTime.set(Calendar.MINUTE, startTimeMin);
        nextTime.set(Calendar.SECOND, 0);
        nextTime.set(Calendar.MILLISECOND, 0);

        // 计划每天截止日期
        Calendar end = Calendar.getInstance();
        end.setTime(nextTime.getTime());
        end.set(Calendar.HOUR_OF_DAY, endTimeHour);
        end.set(Calendar.MINUTE, endTimeMin);
        end.set(Calendar.SECOND, 0);
        end.set(Calendar.MILLISECOND, 0);

        if (lastExecutionTime != null) {

            if (nextTime.getTimeInMillis() <= lastExecutionTime.getTime()) {
                // 递归加一个时间间隔 直到大于当前时间
                addHour(nextTime, lastExecutionTime, executionPoint);

                if (nextTime.getTimeInMillis() == lastExecutionTime.getTime()) {
                    nextTime.add(Calendar.HOUR_OF_DAY, executionPoint);
                }

                end.setTime(nextTime.getTime());
                end.set(Calendar.HOUR_OF_DAY, endTimeHour);
                end.set(Calendar.MINUTE, endTimeMin);
                end.set(Calendar.SECOND, 0);
                end.set(Calendar.MILLISECOND, 0);

                // 超过当天的计划执行截止日期加1天
                if (nextTime.getTimeInMillis() > end.getTimeInMillis()) {
                    nextTime.add(Calendar.HOUR_OF_DAY, -executionPoint);
                    nextTime.set(Calendar.HOUR_OF_DAY, startTimeHour);
                    nextTime.set(Calendar.MINUTE, startTimeMin);
                    nextTime.add(Calendar.DATE, 1);
                }
            }
        }

        // 如果下次执行时间小于当前时间 只能下个月执行
        if (nextTime.getTimeInMillis() < current.getTime()) {

            // 递归加一个时间间隔 直到大于当前时间
            addHour(nextTime, current, executionPoint);

            if (nextTime.getTimeInMillis() > end.getTimeInMillis()) {
                nextTime.add(Calendar.HOUR_OF_DAY, -executionPoint);
                nextTime.set(Calendar.HOUR_OF_DAY, startTimeHour);
                nextTime.set(Calendar.MINUTE, startTimeMin);
                nextTime.add(Calendar.DATE, 1);
            }
        }

        // 如果下次执行日期小于有效开始日期
        if (nextTime.getTimeInMillis() < effectiveDateStart.getTime()) {

            nextTime.setTime(effectiveDateStart);
            nextTime.set(Calendar.HOUR_OF_DAY, startTimeHour);
            nextTime.set(Calendar.MINUTE, startTimeMin);
            nextTime.set(Calendar.SECOND, 0);
            nextTime.set(Calendar.MILLISECOND, 0);
        }

        Date NextExecutionTime = nextTime.getTime();
        // 下个计划执行日期超过有效期 则该任务不需要再执行
        if (null != effectiveDateEnd && NextExecutionTime.getTime() > effectiveDateEnd.getTime()) {
            return null;
        }
        return nextTime.getTime();
    }

    private static void addHour(Calendar nextTime, Date date, int hour) {

        if (nextTime.getTimeInMillis() >= date.getTime()) {
            return;
        }

        nextTime.add(Calendar.HOUR_OF_DAY, hour);

        addHour(nextTime, date, hour);
    }

    /**
     * 按天计算下个执行时间
     *
     * @param effectiveDateStart
     * @param effectiveDateEnd
     * @param lastExecutionTime
     * @param startTimeHour
     * @param startTimeMin
     * @return
     */
    private static Date computeDaySchedule(
        Date effectiveDateStart,
        Date effectiveDateEnd,
        Date lastExecutionTime,
        int startTimeHour,
        int startTimeMin) {

        Calendar nextTime = Calendar.getInstance();

        // 获取计划执行的日期
        Date current = new Date();

        nextTime.setTime(current);

        nextTime.set(Calendar.HOUR_OF_DAY, startTimeHour);
        nextTime.set(Calendar.MINUTE, startTimeMin);
        nextTime.set(Calendar.SECOND, 0);
        nextTime.set(Calendar.MILLISECOND, 0);

        // 如果不是第一次执行 需要判断最近一次执行的时间 与下次执行的时间
        if (lastExecutionTime != null) {

            // 如果下次计划执行日期小于等于上次执行的日期 则将时间设置成上次执行的年月
            if (nextTime.getTimeInMillis() <= lastExecutionTime.getTime()) {
                nextTime.setTime(lastExecutionTime);
                nextTime.set(Calendar.HOUR_OF_DAY, startTimeHour);
                nextTime.set(Calendar.MINUTE, startTimeMin);
                nextTime.set(Calendar.SECOND, 0);
                nextTime.set(Calendar.MILLISECOND, 0);
            }

            // 如果还是小于上次执行时间 则加一个周
            if (nextTime.getTimeInMillis() <= lastExecutionTime.getTime()) {
                nextTime.add(Calendar.DATE, 1);
            }
        }

        // 如果下次执行时间小于当前时间
        if (nextTime.getTimeInMillis() < current.getTime()) {
            nextTime.add(Calendar.DATE, 1);
        }

        // 如果下次执行日期小于有效开始日期
        if (nextTime.getTimeInMillis() < effectiveDateStart.getTime()) {

            nextTime.setTime(effectiveDateStart);
            nextTime.set(Calendar.HOUR_OF_DAY, startTimeHour);
            nextTime.set(Calendar.MINUTE, startTimeMin);
            nextTime.set(Calendar.SECOND, 0);
            nextTime.set(Calendar.MILLISECOND, 0);

            // 如果计划开始日期在同一个月后还比有效开始日期小则加一个月
            if (nextTime.getTimeInMillis() < effectiveDateStart.getTime()) {
                nextTime.add(Calendar.DATE, 1);
            }
        }

        Date NextExecutionTime = nextTime.getTime();
        // 下个计划执行日期超过有效期 则该任务不需要再执行
        if (null != effectiveDateEnd && NextExecutionTime.getTime() > effectiveDateEnd.getTime()) {
            return null;
        }
        return nextTime.getTime();
    }

    /**
     * 按周计算下个执行时间
     *
     * @param executionPoint
     * @param effectiveDateStart
     * @param effectiveDateEnd
     * @param startTime
     * @param startTimeHour
     * @param startTimeMin
     * @return
     */
    private static Date computeWeekSchedule(
        String executionPoint,
        Date effectiveDateStart,
        Date effectiveDateEnd,
        Date startTime,
        int startTimeHour,
        int startTimeMin) {

        Date current = new Date();

        if (!current.after(effectiveDateStart)) {
            current = effectiveDateStart;
        }

        if (startTime == null || !startTime.after(current)) {
            startTime = current;
        }

        Calendar nextTime = Calendar.getInstance();
        nextTime.setTime(startTime);
        nextTime.set(Calendar.HOUR_OF_DAY, startTimeHour);
        nextTime.set(Calendar.MINUTE, startTimeMin);
        nextTime.set(Calendar.SECOND, 0);
        nextTime.set(Calendar.MILLISECOND, 0);

        int dayOfWeek = nextTime.get(Calendar.DAY_OF_WEEK);
        // 如果当前时间是周日则减去一天 时间变为周六 因为周六是一周的最后一天
        if (dayOfWeek == 1) {
            nextTime.add(Calendar.DAY_OF_WEEK, -1);
        }

        String[] days = executionPoint.split(",");
        String firstDay = "";
        for (int i = 0; i < days.length; i++) {
            if (i == 0) {
                firstDay = days[i];
            }
            // 如果是周一到周六
            if (Integer.valueOf(days[i]) < 7) {
                nextTime.set(Calendar.DAY_OF_WEEK, Integer.valueOf(days[i]) + 1);
            } else {
                // 如果是周日 设置成周六并且+1天
                nextTime.set(Calendar.DAY_OF_WEEK, 7);
                nextTime.add(Calendar.DAY_OF_WEEK, 1);
            }

            if (nextTime.getTime().after(current) && nextTime.getTime().after(startTime)) {
                break;
            }
        }

        if (!nextTime.getTime().after(current) || !nextTime.getTime().after(startTime)) {
            nextTime.add(Calendar.DAY_OF_WEEK, 7);
            dayOfWeek = nextTime.get(Calendar.DAY_OF_WEEK);
            // 如果当前时间是周日则减去一天 时间变为周六 因为周六是一周的最后一天
            if (dayOfWeek == 1) {
                nextTime.add(Calendar.DAY_OF_WEEK, -1);
            }
            // 如果是周一到周六
            if (Integer.valueOf(firstDay) < 7) {
                nextTime.set(Calendar.DAY_OF_WEEK, Integer.valueOf(firstDay) + 1);
            } else {
                // 如果是周日 设置成周六并且+1天
                nextTime.set(Calendar.DAY_OF_WEEK, 7);
                nextTime.add(Calendar.DAY_OF_WEEK, 1);
            }
        }

        Date NextExecutionTime = nextTime.getTime();
        // 下个计划执行日期超过有效期 则该任务不需要再执行
        if (null != effectiveDateEnd && NextExecutionTime.getTime() > effectiveDateEnd.getTime()) {
            return null;
        }
        return nextTime.getTime();
    }

    /**
     * 按月计算下个执行周期
     *
     * @param executionPoint
     * @param effectiveDateStart
     * @param effectiveDateEnd
     * @param startTime
     * @param startTimeHour
     * @param startTimeMin
     * @return
     */
    private static Date computeMonthSchedule(
        String executionPoint,
        Date effectiveDateStart,
        Date effectiveDateEnd,
        Date startTime,
        Integer startTimeHour,
        Integer startTimeMin) {

        Date current = new Date();

        if (!current.after(effectiveDateStart)) {
            current = effectiveDateStart;
        }

        if (startTime == null || !startTime.after(current)) {
            startTime = current;
        }

        Calendar nextTime = Calendar.getInstance();
        nextTime.setTime(startTime);
        nextTime.set(Calendar.HOUR_OF_DAY, startTimeHour);
        nextTime.set(Calendar.MINUTE, startTimeMin);
        nextTime.set(Calendar.SECOND, 0);
        nextTime.set(Calendar.MILLISECOND, 0);

        int maxDay = nextTime.getActualMaximum(Calendar.DAY_OF_MONTH);

        String[] days = executionPoint.split(",");

        String firstDay = "";

        for (int i = 0; i < days.length; i++) {

            if (i == 0) {
                firstDay = days[i];
            }

            if (NumberUtils.isNumber(days[i])) {
                if (maxDay <= Integer.valueOf(days[i])) {
                    nextTime.set(Calendar.DAY_OF_MONTH, maxDay);
                } else {
                    nextTime.set(Calendar.DAY_OF_MONTH, Integer.valueOf(days[i]));
                }
            } else {
                nextTime.set(Calendar.DAY_OF_MONTH, maxDay);
            }

            if (nextTime.getTime().after(current) && nextTime.getTime().after(startTime)) {
                break;
            }
        }

        if (!nextTime.getTime().after(current) || !nextTime.getTime().after(startTime)) {
            nextTime.set(Calendar.DAY_OF_MONTH, 1);
            nextTime.add(Calendar.MONTH, 1);
            maxDay = nextTime.getActualMaximum(Calendar.DAY_OF_MONTH);
            if (NumberUtils.isNumber(firstDay)) {
                if (maxDay <= Integer.valueOf(firstDay)) {
                    nextTime.set(Calendar.DAY_OF_MONTH, maxDay);
                } else {
                    nextTime.set(Calendar.DAY_OF_MONTH, Integer.valueOf(firstDay));
                }
            } else {
                nextTime.set(Calendar.DAY_OF_MONTH, maxDay);
            }
        }

        Date NextExecutionTime = nextTime.getTime();
        // 下个计划执行日期超过有效期 则该任务不需要再执行
        if (null != effectiveDateEnd && NextExecutionTime.getTime() > effectiveDateEnd.getTime()) {
            return null;
        }
        return nextTime.getTime();
    }

    public static void main(String[] args) {

    /*
    {"cycle":0,"cycleDependence":0,"effectiveDateEnd":253402099200000,"effectiveDateStart":1706457600000,"endTimeHour":"","endTimeMin":"","execPoint":"29,30,31","startTimeHour":"15","startTimeMin":"30","status":null}
     */

        Instant instant = Instant.ofEpochMilli(253402099200000L);
        Date end = Date.from(instant);

        Instant instant2 = Instant.ofEpochMilli(1706817600000L);
        Date from = Date.from(instant2);
        System.out.println(from);
        // 0-月
        int cycle = 0;
        String executionPoint =
            "27,30,31";
        int startTimeHour = 13;
        int startTimeMin = 22;
        int endTimeHour = 18;
        int endTimeMin = 8;

        //    for (int i = 1; i <= 7; i++) {

        EngineScheduleInfo engineScheduleInfo = new EngineScheduleInfo();
        engineScheduleInfo.setCycle(cycle);
        engineScheduleInfo.setEffectiveDateEnd(end);
        engineScheduleInfo.setEffectiveDateStart(from);
        engineScheduleInfo.setCycle(cycle);
        engineScheduleInfo.setStartTimeMin(startTimeMin);
        engineScheduleInfo.setStartTimeHour(startTimeHour);
        engineScheduleInfo.setExecPoint(executionPoint);

        Date date =
            getNextExecutionTime(engineScheduleInfo, null);

        System.out.println(DateUtil.formatDateTime(date));

    }
}
